#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Networked Data Processing Daemon
#
# Copyright (c) 2011 Sergey Simonenko <gforgx@gmail.com>
# Distributed under the terms of GNU General Public License Version 3

import os
import time
import syslog
import threading
import ConfigParser

from ndp.common import *
from ndp.const import *
from ndp.xmlrpc import ThreadedXMLRPCServer, RequestHandler
from ndp.db import Cache

client_dispatcher = {}
database_dispatcher = {}

client_ignore_list = []

for database in ('log', 'match'):
	url = req_option(database + '_db')

	log_msg('Initializing %s_db cache object (%s)...' % (database, url))

	ignored_column_list = []

	if database == 'log':
		ignored_column_list.append('date')
		ignored_column_list.append('item_id')

	database_dispatcher[database] = Cache(url, req_option('match_column'), ignored_column_list)

class SyncThread(threading.Thread):
	def run(self):
		while True:
			try:
				log_msg('Syncing (%i active client connections)' % len(client_dispatcher))

				for i in client_dispatcher:
					log_msg(i)

				for reg in client_dispatcher.keys():
					# Increment check_count per client so we'll know
					# how many times it's item list is synced in terms
					# of active session
					client_dispatcher[reg]['check_count'] += 1

					cur_len = client_dispatcher[reg]['cur_count']

					if cur_len == 0:
						continue

					delta_t = int(time.time()) - client_dispatcher[reg]['last_update']

					client_dispatcher[reg]['delta_t'] = delta_t

					if cur_len <= client_dispatcher[reg]['count'] and delta_t > 30:
						log_msg('%s seems to be idle, unregistering it (%i updates in %i syncs, delta is %i)' % (reg, client_dispatcher[reg]['cur_count'], client_dispatcher[reg]['check_count'], client_dispatcher[reg]['delta_t']))
						del client_dispatcher[reg]

						call_script(reg, 0)
							
						continue

					client_dispatcher[reg]['count'] = cur_len

				time.sleep(int(req_option('sync_period')))

			except Exception, err:
				log_msg('Sync thread failed (%s!)! Continuing anyway!' % str(err))

def Register(ip_addr, client_version):
	if ip_addr in client_ignore_list:
		log_msg('Client %s was added to ignore list, ignoring him!' % ip_addr)

		return 0

	version = req_option('version')

	if version == client_version:
		client_dispatcher[ip_addr] = {'cur_count': 0, 'count': 0, 'check_count': 0, 'allow': {}, 'last_update': int(time.time()), 'delta_t': 0, 'match_count': 0}
		
		log_msg('Registered %s (protocol version %s)' % (ip_addr, client_version))

		return MSG_REGISTER_OK

	else:
		update_url = req_option('update_url')
			
		log_msg('%s is running outdated protocol version (%s), server protocol version is %s, sending %s!' % (ip_addr, client_version, version, req_option('update_url')))
		
		return update_url

def Update(ip_addr, item_list):
	if not ip_addr in client_dispatcher:
		log_msg('Client from %s isn\'t registered, ignoring him and sending UNREGISTERED message!' % ip_addr)

		return MSG_UNREGISTERED

	match_column = req_option('match_column')

	last_update = client_dispatcher[ip_addr]['last_update']

	# Incrementing peer update counter
	client_dispatcher[ip_addr]['cur_count'] += 1
	client_dispatcher[ip_addr]['last_update'] = int(time.time())

	log_msg('Got update #%i (%i items) from %s (update delta is %i s)' % (client_dispatcher[ip_addr]['cur_count'], len(item_list), ip_addr, client_dispatcher[ip_addr]['last_update'] - last_update))

	return_item_list = []
	status_list = {}

	for item in item_list:
		item = item.encode('utf-8')

		data = {}
		query = {}

		for part in item.split('|'):
			p, v = part.split(':', 1)

			p = 'item_' + p
			v = v.replace('\\', '/')

			data[p] = v

			if p == match_column:
				query[p] = v

		data['item_client_ip'] = ip_addr

		# Log everything to the database (cache'll handle duplicates on its own)
		database_dispatcher['log'].put(data)

		# Now, query match cache against match_column of record
		r = database_dispatcher['match'].get(query)

		for i in r:
			log_msg('%s from %s matches match database record with description set to "%s" (%s = %s)!' % (item.encode('utf-8'), ip_addr, i['description'], match_column, data[match_column]))				
		
		if len(r) != 0:
			return_item_list.append(item)

			client_dispatcher[ip_addr]['match_count'] += 1

			if client_dispatcher[ip_addr]['match_count'] == int(req_option('unreg_count')):
				log_msg('Client %s added to ignore list (match_count is %s)!' % (ip_addr, client_dispatcher[ip_addr]['match_count']))
				client_ignore_list.append(ip_addr)

				del client_dispatcher[ip_addr]

	call_script(ip_addr, int(len(return_item_list) == 0))

	# Useful info
	log_msg('Processing update #%i (%i items) from %s took %i seconds' % (client_dispatcher[ip_addr]['cur_count'], len(item_list), ip_addr, int(time.time()) - client_dispatcher[ip_addr]['last_update']))

	# Let's play boomerang!
	return return_item_list

def Unregister(ip_addr, i_dont_know_what_this_variable_should_do_left_for_backwards_compability):
	del client_dispatcher[ip_addr]

	call_script(ip_addr, 0)
	log_msg('Unregistered %s' % ip_addr)

	# Or maybe return i_dont_know_what_this_variable_should_do_left_for_backwards_compability back here? :)
	return MSG_UNREGISTER_OK

if __name__ == '__main__':
	addr, port = req_option('listen').split(':')

	server = ThreadedXMLRPCServer((addr, int(port)), requestHandler=RequestHandler)
	server.register_introspection_functions()

	server.register_function(Register)
	server.register_function(Unregister)
	server.register_function(Update)

	SyncThread().start()	

	log_msg('Serving at %s' % req_option('listen'))

	server.serve_forever()

